﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace UnitTestSample2
{
    //テスト開始前イベント
    public class TestStartingEventArgs : EventArgs
    {
    }

    //テスト進捗更新イベント
    public class TestProgressEventArgs : EventArgs
    {
        public string Name { get; private set; }
        public bool Result { get; private set; }
        public Exception Error { get; private set; }

        public TestProgressEventArgs(string name, bool result, Exception exception)
        {
            this.Name = name;
            this.Result = result;
            this.Error = exception;
        }
    }

    //テスト終了イベント
    public class TestFinishedEventArgs : EventArgs
    {
    }

    //ユニットテスト実行エラー
    public class UnitTestAsertException : Exception
    {
        public UnitTestAsertException(string mes)
            : base(mes)
        {
        }
    }

    //テストクラス
    public class UnitTest
    {
        public void AssertEqual(object expected, object actual)
        {
            if (!expected.Equals(actual))
                throw new UnitTestAsertException("[AssertError] expected value is " + expected + " but " + actual);
        }

        public void AssertNull(object val)
        {
            if (val != null)
                throw new UnitTestAsertException("[AssertError] expected value is not null    (value:" + val + ")");
        }

        public void AssertOK()
        {
        }

        public void AssertNG(string msg)
        {
            throw new UnitTestAsertException("[AssertError] " + msg);
        }
    }

    //テストランナーインターフェイス
    public interface ITestRunner
    {
        event EventHandler<TestStartingEventArgs> TestStarting;
        event EventHandler<TestProgressEventArgs> TestProgress;
        event EventHandler<TestFinishedEventArgs> TestFinished;

        void LoadAssembly(List<System.Reflection.Assembly> assemblys);
        void Run();
    }

    //テストランナー
    public class TestRunner
    {
        private class DefaultTestRunner : ITestRunner
        {
            public event EventHandler<TestStartingEventArgs> TestStarting;
            public event EventHandler<TestProgressEventArgs> TestProgress;
            public event EventHandler<TestFinishedEventArgs> TestFinished;

            private class TestInfo
            {
                public Type TestClassType;
                public List<MethodInfo> TestMethodInfos = new List<MethodInfo>();
                public TestInfo() { }
            }

            private List<TestInfo> tests = new List<TestInfo>();

            public void LoadAssembly(List<System.Reflection.Assembly> assemblys)
            {
                for (int i = 0; i < assemblys.Count; i++)
                    LoadTestClass(assemblys[i]);
            }

            private void LoadTestClass(Assembly asm)
            {
                for (int i = 0; i < asm.GetTypes().Length; i++)
                {
                    Type type = asm.GetTypes()[i];
                    if (type.BaseType == null || !type.BaseType.Equals(typeof(UnitTest)))
                        continue;
                    LoadTestMethod(type);
                }
            }

            private void LoadTestMethod(Type type)
            {
                var test = new TestInfo();
                test.TestClassType = type;

                for (int i = 0; i < type.GetMethods().Length; i++)
                {
                    MethodInfo mi = type.GetMethods()[i];
                    if (!mi.Name.StartsWith("test_"))
                        continue;
                    test.TestMethodInfos.Add(mi);
                }
                tests.Add(test);
            }

            public void Run()
            {
                if (TestStarting != null)
                    TestStarting(this, new TestStartingEventArgs());

                for (int i = 0; i < tests.Count; i++)
                    RunTheTest(tests[i]);

                if (TestFinished != null)
                    TestFinished(this, new TestFinishedEventArgs());
            }

            private void RunTheTest(TestInfo info)
            {
                Type type = info.TestClassType;
                UnitTest test = type.Assembly.CreateInstance(type.FullName) as UnitTest;

                for (int i = 0; i < info.TestMethodInfos.Count; i++)
                {
                    Exception err = null;
                    bool result = true;
                    var mi = info.TestMethodInfos[i];

                    try
                    {
                        mi.Invoke(test, null);
                    }
                    catch (TargetInvocationException e)
                    {
                        if (e.InnerException.GetType() != typeof(UnitTestAsertException))
                            throw e;
                        err = e.InnerException;
                        result = false;
                    }

                    if (TestProgress != null)
                        TestProgress(this, new TestProgressEventArgs(type.Name + "." + mi.Name + "()", result, err));
                }
            }
        }

        private static ITestRunner current = null;
        public static ITestRunner Current
        {
            get
            {
                if (current == null)
                    current = new DefaultTestRunner();
                return current;
            }
            set
            {
                current = value;
            }
        }
    }
}

